package com.wing.tools.screenlive.encoder;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.media.MediaCodec;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.os.Build;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceView;

import com.wing.tools.screenlive.encoder.SurfaceManager;
import com.wing.tools.screenlive.encoder.TextureManager;
import com.wing.tools.screenlive.encoder.VideoEncodeWorker;
import com.wing.tools.screenlive.encoder.VideoEncoder;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.Semaphore;

/**
 * Created by wejust on 2018/1/4.
 */

public class CameraEncoder extends VideoEncoder {
    private static final String TAG = "VIDEO_ENCODER_TAG";

    public boolean enableStreaming = true;

    protected Camera camera;
    protected Thread cameraThread;
    protected Looper cameraLooper;
    protected boolean cameraOpenedManually = true;
    protected int cameraId = 0;

    protected boolean unlocked = false;
    protected boolean previewStarted = false;
    protected boolean updated = false;
    protected boolean streaming = false;

    private VideoEncodeWorker.IReceiver mReceiver;
    private SurfaceManager mSurfaceManager;
    private TextureManager mTextureManager;

    private EGLContext mSharedContext;
    private int mGLESVersion = 3;

    private long mMilliSconds = 0;
    private SurfaceTexture surfaceTexture;
    private Context mContext;

    public CameraEncoder(Context ctx, VideoEncodeWorker.IReceiver receiver, int cameraID) {
        super(receiver);

        this.mReceiver = receiver;
        this.cameraId = cameraID;
        this.mContext = ctx;
    }

    @Override
    protected void onStart() {
        startPreview();
    }

    @Override
    protected void onEnd() {
        stopPreview();
    }

    public void init(EGLContext sharedContext){
        mSharedContext = sharedContext;
        if(sharedContext == null){
            mSharedContext = EGL14.EGL_NO_CONTEXT;
        }

        ActivityManager am =(ActivityManager) this.mContext.getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo info = am.getDeviceConfigurationInfo();
//      info.reqGlEsVersion= 0x00010001  //代表opengles 1.1
//      info.reqGlEsVersion=  0x00020000     //代表opengles 2.0
        mGLESVersion = info.reqGlEsVersion >> 0x10;

        mTextureManager = new TextureManager();
        mTextureManager.init();
        mSurfaceManager = new SurfaceManager(mWorker.getSurface(), mSharedContext, mGLESVersion);
    }

    @Override
    public void begin(){
        super.begin();
        startPreview();
    }

    @Override
    public void end(){
        super.end();
        stopPreview();
    }

    public int getTextureID(){
        return mTextureManager.getTextureId();
    }

    private synchronized void startPreview() throws RuntimeException {
        cameraOpenedManually = true;
        if (!previewStarted) {
            createCamera();
            updateCamera();
        }
    }

    /**
     * Stops the preview.
     */
    private synchronized void stopPreview() {
        cameraOpenedManually = false;
        stop();
    }

    private void openCamera() throws RuntimeException {
        final Semaphore lock = new Semaphore(0);
        final RuntimeException[] exception = new RuntimeException[1];
        cameraThread = new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                cameraLooper = Looper.myLooper();
                try {
                    camera = Camera.open(cameraId);
//                    List<Camera.Size> previewSizes = camera.getParameters().getSupportedPreviewSizes();
//                    Log.d("111", "bbb");
                } catch (RuntimeException e) {
                    exception[0] = e;
                } finally {
                    lock.release();
                    Looper.loop();
                }
            }
        });
        cameraThread.start();
        lock.acquireUninterruptibly();
    }

    protected synchronized void createCamera() throws RuntimeException {
        if (camera == null) {
            openCamera();
            updated = false;
            unlocked = false;
            camera.setErrorCallback(new Camera.ErrorCallback() {

                @Override
                public void onError(int error, Camera camera) {
                    // On some phones when trying to use the camera facing front the media server
                    // will die. Whether or not this callback may be called really depends on the
                    // phone
                    if (error == Camera.CAMERA_ERROR_SERVER_DIED) {
                        // In this case the application must release the camera and instantiate a
                        // new one
                        Log.e(TAG, "Media server died !");
                        // We don't know in what thread we are so stop needs to be synchronized
                        cameraOpenedManually = false;
                        stop();
                    } else {
                        Log.e(TAG, "Error unknown with the camera: " + error);
                    }
                }
            });

            try {
                // If the phone has a flash, we turn it on/off according to flashEnabled
                // setRecordingHint(true) is a very nice optimization if you plane to only use
                // the Camera for recording
                Camera.Parameters parameters = camera.getParameters();
                if (parameters.getFlashMode() != null) {
                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                }
                parameters.setRecordingHint(true);
                camera.setParameters(parameters);
                //camera.setDisplayOrientation(270);

                try {
                    int texId = mTextureManager.createTexture();
                    surfaceTexture = new SurfaceTexture(texId);
                    camera.setPreviewTexture(surfaceTexture);

                    if(enableStreaming) {
                        surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                            @Override
                            public void onFrameAvailable(SurfaceTexture surfaceTexture) {

                            }
                        });
                        camera.setPreviewCallback(new Camera.PreviewCallback() {
                            @Override
                            public void onPreviewFrame(byte[] bytes, Camera camera) {
                                mHandler.sendEmptyMessage(0);
                            }
                        });
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (RuntimeException e) {
                destroyCamera();
                throw e;
            }
        }
    }

    @Override
    protected void onMessage(Message message) {
        if(System.currentTimeMillis() - mMilliSconds > 2500) {
            _drawFrame();
            mMilliSconds = System.currentTimeMillis();
        }
    }

    private long computePresentationTimeNsec(int frameIndex) {
        final long ONE_BILLION = 1000000000;
        return frameIndex * ONE_BILLION / mFPS;
    }

    private void _drawFrame(){
        mIndex++;

        mSurfaceManager.makeCurrent();
        mTextureManager.drawFrame(-1);
        mSurfaceManager.setPresentationTime(computePresentationTimeNsec(mIndex));
        mSurfaceManager.swapBuffer();
    }

    protected synchronized void destroyCamera() {
        if (camera != null) {
            if (streaming) {
                //super.stop();
            }
            lockCamera();
            camera.stopPreview();
            try {
                camera.release();
            } catch (Exception e) {
                Log.e(TAG, e.getMessage() != null ? e.getMessage() : "unknown error");
            }
            camera = null;
            cameraLooper.quit();
            unlocked = false;
            previewStarted = false;
        }
    }

    protected synchronized void updateCamera() throws RuntimeException {
        // The camera is already correctly configured
        if (updated) {
            return;
        }

        if (previewStarted) {
            previewStarted = false;
            camera.stopPreview();
        }

        Camera.Parameters parameters = camera.getParameters();

        //mSurfaceView.requestAspectRatio(mWidth / mHeight);

        //parameters.setPreviewFormat(ImageFormat.YUV_420_888);
        parameters.setPreviewSize(mWidth, mHeight);
        //parameters.setPreviewFpsRange(30, 30);

        try {
            camera.setParameters(parameters);
            //camera.setDisplayOrientation(270);

            camera.startPreview();
            previewStarted = true;
            updated = true;
        } catch (RuntimeException e) {
            destroyCamera();
            throw e;
        }
    }

    protected void lockCamera() {
        if (unlocked) {
            try {
                camera.reconnect();
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            unlocked = false;
        }
    }

    protected void unlockCamera() {
        if (!unlocked) {
            Log.d(TAG, "Unlocking camera");
            try {
                camera.unlock();
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            unlocked = true;
        }
    }
}
